/*****************************************************************************
 *  ENTROPY - emerging network to reduce orwellian potency yield
 *
 *  Copyright (C) 2002 Juergen Buchmueller <pullmoll@stop1984.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *	$Id: entropy.c,v 1.5 2005/10/18 09:52:37 pullmoll Exp $
 *****************************************************************************/
#include "osd.h"
#include "config.h"
#include "shmalloc.h"
#include "temp.h"
#include "unicode.h"
#include "store.h"
#include "file.h"
#include "ek5.h"
#include "sha1.h"
#include "base64.h"
#include "client.h"
#include "peer.h"
#include "proxy.h"
#include "news.h"
#include "logger.h"
#include "ipv4_list.h"

#ifdef	__CYGWIN__
	int autorun = 0;
#endif

static void main_exit(int sig)
{
	FUN("main_exit");

	signal(sig, SIG_DFL);
	LOG(L_ERROR,("*** {%d} signal %s ***\n",
		(int)getpid(), signal_name(sig)));
	shm_pool_exit();
	_exit(0);
}

typedef struct integrity_test_s {
	const char *data;
	size_t size;
	const char *result;
}	integrity_test_t;

static const integrity_test_t ek5_tests[] = {
	{"", 0,
		"47cfb6350525d12ee8ce5f3d50f77ad8"},
	{"a", 1,
		"42a21275da5d7e4797a2522d57225781"},
	{"abc", 3,
		"ec027ebb8e60e7d8d78c34af288ed35d"},
	{"message digest", 14,
		"da95f8db8656bf6241a6d96991c845da"},
	{"abcdefghijklmnopqrstuvwxyz", 26,
		"d63e6775b30b69705b40b91f3178ee08"},
	{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62,
		"42b1bd6f1cc7291e21105dc20296c265"},
	/* 80 as 8 times 10 chars */
	{"0123456789", 80,
		"e687769cd60390c9d2d4381aed3a8a49"},
	/* 80 as 2 times 40 chars */
	{"0123456789012345678901234567890123456789", 80,
		"e687769cd60390c9d2d4381aed3a8a49"},
	/* 1000000 as 1000000 times 1 char */
	{"a", 1000000,
		"efc7b659fe741cc33c7097f3a6669df0"},
	/* 1000000 as 100000 times 10 chars */
	{"aaaaaaaaaa", 1000000,
		"efc7b659fe741cc33c7097f3a6669df0"}
};

static const integrity_test_t sha1_tests[] = {
	{"", 0,
		"da39a3ee5e6b4b0d3255bfef95601890afd80709"},
	{"a", 1,
		"86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"},
	{"abc", 3,
		"a9993e364706816aba3e25717850c26c9cd0d89d"},
	{"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56,
		"84983e441c3bd26ebaae4aa1f95129e5e54670f1"},
	/* 1000000 as 1000000 times 1 char */
	{"a", 1000000,
		"34aa973cd4c4daa4f61eeb2bdbad27316534016f"},
	/* 1000000 as 500000 times 20 chars */
	{"aaaaaaaaaaaaaaaaaaaa", 1000000,
		"34aa973cd4c4daa4f61eeb2bdbad27316534016f"}
};

/* Check the hashes for integrity */
int integrity(void)
{
	ek5_state_t es;
	ek5_digest_t ek5;
	sha1_state_t ss;
	sha1_digest_t sha1;
	char result[2*SHA1SIZE+1];
	size_t i, j, k;
	FUN("integrity");

	for (i = 0; i < sizeof(ek5_tests)/sizeof(ek5_tests[0]); i++) {
		const integrity_test_t *t = &ek5_tests[i];
		ek5_init(&es);
		k = strlen(t->data);
		if (k == t->size) {
			ek5_append(&es, t->data, t->size);
		} else {
			for (j = 0; j < t->size; j += k)
				ek5_append(&es, t->data, k);
		}
		ek5_finish(&es, &ek5);
		strcpy(result, ek5_hexstr(&ek5));
		if (32 != strlen(result) || 0 != strcmp(t->result, result)) {
			LOG(L_ERROR,("EK5 test #%d failed\n" \
				"msg: %s (%u)\n" \
				"exp: %s\n" \
				"got: %s\n",
				(int)i,
				t->data, (unsigned)t->size,
				t->result,
				result));
			die(1, "EK5 integrity test #%d failed\n", i);
		}
	}

	for (i = 0; i < sizeof(sha1_tests)/sizeof(sha1_tests[0]); i++) {
		const integrity_test_t *t = &sha1_tests[i];
		sha1_init(&ss);
		if (t->size < 100) {
			sha1_append(&ss, t->data, t->size);
		} else {
			k = strlen(t->data);
			for (j = 0; j < t->size; j += k)
				sha1_append(&ss, t->data, k);
		}
		sha1_finish(&ss, &sha1);
		strcpy(result, sha1_hexstr(&sha1));
		if (40 != strlen(result) || 0 != strcmp(t->result, result)) {
			LOG(L_ERROR,("SHA1 test #%d failed\n" \
				"msg: %s (%u)\n" \
				"exp: %s\n" \
				"got: %s\n",
				(int)i,
				t->data, (unsigned)t->size,
				t->result,
				result));
			die(1, "SHA1 integrity test #%d failed\n", i);
		}
	}

	return 0;
}

int main(int argc, char **argv)
{
	char filename[256];
	int i, stage = 0, rc = 0;
	FUN("main");

	/* set up the configuration defaults */
	if (-1 == conf_init(argc, argv)) {
		fprintf(stderr, "configuration init failed\n");
		exit(1);
	}

	for (i = 1; i < argc; i++) {
		if (0 == strcasecmp(argv[i], "-k") ||
			0 == strcasecmp(argv[i], "--kill")) {
			rc = osd_proc_exit();
			if (0 == rc) {
				info("%s shut down\n", PROGNAME);
			} else {
				info("%s could not be shut down\n", PROGNAME);
			}
			exit(rc);
		} else if (0 == strcasecmp(argv[i], "-v") ||
			0 == strcasecmp(argv[i], "--verbose")) {
			verbose = 1;
#ifdef	__CYGWIN__
		} else if (0 == strcasecmp(argv[i], "-a") ||
			0 == strcasecmp(argv[i], "--autorun")) {
			autorun = 1;
#endif
		}
	}

	if (0 != (rc = shm_pool(argv))) {
		fprintf(stderr, "shared memory initialization failed\n");
#if	!HAVE_MMAP
		fprintf(stderr, "check for old shm segments using 'ipcs'\n");
#endif
#if	USE_SVID_SEMAPHORES
		fprintf(stderr, "Check for old semaphores using 'ipcs'.\n");
		fprintf(stderr, "If this is your first time running Entropy, you may need to raise\n");
		fprintf(stderr, "the semaphore limits in your kernel:  add 2 to SEMMNI and 15 to SEMMNS.\n");
#endif
		exit(1);
	}

	info("program:\t%s\n", g_conf->program);
	info("progpath:\t%s\n", g_conf->progpath);
	info("configpath:\t%s\n", g_conf->configpath);

#if	DEBUG
	for (i = 1; i < argc; i++) {
		if (0 == strcasecmp(argv[i], "-l") ||
			0 == strcasecmp(argv[i], "--loglevel")) {
			if (i + 1 < argc) {
				g_conf->loglevel[0] = atoi(argv[i+1]);
				i++;
			}
		} else if (0 == strncmp(argv[i], "-l", 2)) {
			g_conf->loglevel[0] = atoi(&argv[i][2]);
		} else if (0 == strncmp(argv[i], "--loglevel", 10)) {
			g_conf->loglevel[0] = atoi(&argv[i][10]);
		}
	}
#endif

	pm_snprintf(filename, sizeof(filename), "%s%s.log",
		g_conf->progpath, g_conf->progname);
	unlink(filename);
	pm_snprintf(filename, sizeof(filename),
		g_conf->configfile);
	info("config:\t\t%s\n", filename);

	conf_read(argc, argv, filename);
	unlink(g_conf->logfile);
	info("runpath:\t%s\n", g_conf->runpath);
	info("storepath:\t%s\n", g_conf->storepath);
	info("temppath:\t%s\n", g_conf->temppath);
	info("logfile:\t%s\n", g_conf->logfile);

	for (i = 1; i < argc; i++) {
		if (0 == strcasecmp(argv[i], "-sc") ||
			0 == strcasecmp(argv[i], "--showconfig")) {
			if (i + 1 < argc) {
				conf_write(argv[i+1]);
				i++;
			} else {
				conf_write("-");	/* to stdout */
			}
			exit(0);
		}
	}

	set_signal_handler(SIGHUP, main_exit);
	set_signal_handler(SIGINT, main_exit);
	set_signal_handler(SIGPIPE, main_exit);
	set_signal_handler(SIGALRM, main_exit);
	set_signal_handler(SIGTERM, main_exit);

	LOG(L_NORMAL,("****** startup ******\n"));
	info("****** startup ******\n");

	integrity();

	stage++;
	LOG(L_NORMAL,("#%2d: reading configuration\n", stage));
	info("#%2d: reading configuration: ", stage);
	if (0 != (rc = conf_read(argc, argv, filename))) {
		LOG(L_ERROR,("conf_read('%s') failed\n", filename));
		die(stage, "conf_read('%s') failed\n", filename);
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init proc\n", stage));
	info("#%2d: init proc: ", stage);
	if (0 != (rc = osd_proc_init(argc, argv))) {
		LOG(L_ERROR,("osd_proc_init failed\n"));
		die(stage, "osd_proc_init() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init temp\n", stage));
	info("#%2d: init temp: ", stage);
	if (0 != (rc = temp())) {
		LOG(L_ERROR,("temp startup failed\n"));
		die(stage, "temp() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init fec\n", stage));
	info("#%2d: init fec: ", stage);
	if (0 != (rc = fec())) {
		LOG(L_ERROR,("fec startup failed\n"));
		die(stage, "fec() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init crypto\n", stage));
	info("#%2d: init crypto: ", stage);
	if (0 != (rc = crypto())) {
		LOG(L_ERROR,("crypto() call failed\n"));
		die(stage, "crypto() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init RSA\n", stage));
	info("#%2d: init RSA: ", stage);
	if (0 != (rc = rsa_setup())) {
		LOG(L_ERROR,("rsa_setup() call failed\n"));
		die(stage, "rsa_setup() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init store\n", stage));
	info("#%2d: init store: ", stage);
	if (0 != (rc = store())) {
		LOG(L_ERROR,("store() failed\n"));
		die(stage, "store() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init peer blacklist\n", stage));
	info("#%2d: init peer blacklist: ", stage);
	if ((g_conf->blacklist != NULL) && (strlen(g_conf->blacklist) > 0)) {
		if (0 > (rc = ipv4_black_list_read(g_conf->blacklist))) {
			LOG(L_ERROR,("ipv4_black_list_read() failed\n"));
			die(stage, "ipv4_black_list_read() failed\n");
		} else {
			info("ok\n", stage);
		}
	} else {
		info("none defined\n", stage);
	}

	stage++;
	LOG(L_NORMAL,("#%2d: init peer whitelist\n", stage));
	info("#%2d: init peer whitelist: ", stage);
	if ((g_conf->whitelist != NULL) && (strlen(g_conf->whitelist) > 0)) {
		if (0 > (rc = ipv4_white_list_read(g_conf->whitelist))) {
			LOG(L_ERROR,("ipv4_white_list_read() failed\n"));
			die(stage, "ipv4_white_list_read() failed\n");
		} else {
			info("ok\n", stage);
		}
	} else {
		info("none defined\n", stage);
	}

	stage++;
	LOG(L_NORMAL,("#%2d: init peer\n", stage));
	info("#%2d: init peer: ", stage);
	if (0 != (rc = peer())) {
		LOG(L_ERROR,("peer() failed\n"));
		die(stage, "peer() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init client\n", stage));
	info("#%2d: init client: ", stage);
	if (0 != (rc = client())) {
		LOG(L_ERROR,("client() failed\n"));
		die(stage, "client() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init i18n\n", stage));
	info("#%2d: init i18n: ", stage);
	if (0 != (rc = i18n(g_conf->configpath))) {
		LOG(L_ERROR,("i18n('%s') failed\n", g_conf->configpath));
		die(stage, "i18n('%s') failed\n", g_conf->configpath);
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init mime_types\n", stage));
	info("#%2d: init mime_types: ", stage);
	if (0 != (rc = mime_types(g_conf->configpath))) {
		LOG(L_ERROR,("mime_types('%s') failed\n", g_conf->configpath));
		die(stage, "mime_types('%s') failed\n", g_conf->configpath);
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init news\n", stage));
	info("#%2d: init news: ", stage);
	if (0 != (rc = news())) {
		LOG(L_ERROR,("news() failed\n"));
		die(stage, "news() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init unicode\n", stage));
	info("#%2d: init unicode: ", stage);
	if (0 != (rc = unicode())) {
		LOG(L_ERROR,("unicode startup failed\n"));
		die(stage, "unicode() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init gif\n", stage));
	info("#%2d: init gif: ", stage);
	if (0 != (rc = gif())) {
		LOG(L_ERROR,("gif startup failed\n"));
		die(stage, "gif() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init png\n", stage));
	info("#%2d: init png: ", stage);
	if (0 != (rc = png())) {
		LOG(L_ERROR,("png startup failed\n"));
		die(stage, "png() failed\n");
	}
	info("ok\n", stage);

	stage++;
	LOG(L_NORMAL,("#%2d: init proxy\n", stage));
	info("#%2d: init proxy: ", stage);
	if (0 != (rc = proxy())) {
		LOG(L_ERROR,("proxy() failed\n"));
		die(stage, "proxy() failed\n");
	}
	info("ok\n", stage);

	LOG(L_NORMAL,("****** complete ******\n"));
	info("****** complete ******\n");

	info("%s is running in process group %d\n",
		g_conf->program, g_proc->pgrp);
#ifdef	__CYGWIN__
#define	CYGSTART "cygstart.exe"
	if (0 != autorun) {
		char *_argv[3];
		_argv[0] = xcalloc(MAXPATHLEN, sizeof(char));
		_argv[1] = xcalloc(MAXPATHLEN, sizeof(char));
		_argv[2] = NULL;
		pm_snprintf(_argv[0], MAXPATHLEN, "%s%s",
			g_conf->progpath, CYGSTART);
		if (0 == strcmp(g_conf->proxyhost, "0.0.0.0") ||
			0 == strcmp(g_conf->proxyhost, "IPADDR_ANY")) {
			pm_snprintf(_argv[1], MAXPATHLEN, "http://%s:%d/",
				"127.0.0.1", g_conf->proxyport);
		} else {
			pm_snprintf(_argv[1], MAXPATHLEN, "http://%s:%d/",
				g_conf->proxyhost, g_conf->proxyport);
		}
		info("running %s %s\n", _argv[0], _argv[1]);
		execvp(_argv[0], _argv);
	}
#endif
	return 0;
}
